package com.study.shiro.config;

import com.study.shiro.controller.PubController;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * shiro 全局配置
 *
 * @author huanggy
 * @date 2020/2/10 10:45
 */
@Configuration
public class ShiroConfig {

    /**
     * 配置过滤器
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        // 如果访问某个接口需要登录却没有登录, 调用此接口, 如果不是前后分离项目, 可以直接跳转 jsp 页面
        shiroFilterFactoryBean.setLoginUrl("/pub/needLogin");

        // 登录成功跳转的 url, 如果前后分离则不需要这个调用
        shiroFilterFactoryBean.setSuccessUrl("/");

        // 访问的接口没有权限, 调用此接口
        shiroFilterFactoryBean.setUnauthorizedUrl("/pub/noPermission");

        // 设置自定义 RolesFilter
        LinkedHashMap<String, Filter> rolesFilter = new LinkedHashMap<>();
        rolesFilter.put("customerRoles", new CustomerRolesFilter());
        shiroFilterFactoryBean.setFilters(rolesFilter);

        /*
         * 配置过滤链, 配置的过滤链放在 Map 里面, 根据接口路径, 匹配出指定的过滤器执行
         *  坑一: 必须使用 LinkedHashMap, 因为是 HashMap 因为无序, 所以有的接口就会匹配到 /** 走 authc 过滤器
         *  坑二: 过滤链顺序执行, 所以 /** 放在最后面
         */
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 退出
        filterChainDefinitionMap.put("/pub/logout", "logout");
        // 匿名访问, 不需要登录
        filterChainDefinitionMap.put("/pub/**", "anon");
        // 登录用户才可以访问
        filterChainDefinitionMap.put("/authc", "authc");
        // 需要 admin 角色才可以访问
        filterChainDefinitionMap.put("/admin/**", "roles[admin]");
        // 需要 order 权限才可以访问
        filterChainDefinitionMap.put("/order/**", "perms[order]");

        // 需要 admin 和 root 角色才可以访问
        filterChainDefinitionMap.put("/root/**", "roles[admin,root]");
        // 同时具有 user_add、user_update、user_delete 权限才可以访问
        filterChainDefinitionMap.put("/user/**", "perms[user_update,user_add,user_delete]");

        // 具有 admin 或 root 角色可以访问, 多选 1
        filterChainDefinitionMap.put("/customerRolesFilter/**", "customerRoles[admin,root]");

        // 全局配置
        filterChainDefinitionMap.put("/**", "authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    /**
     * SecurityManager 环境
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();

        // 如果不是前后分离可以不设置 SessionManager
        defaultSecurityManager.setSessionManager(sessionManager());

        // 使用自定义的 CacheManager
        defaultSecurityManager.setCacheManager(cacheManager());

        // 设置 Realm, 写在后面不然某些情况会不生效(网上有这样说)
        defaultSecurityManager.setRealm(customerRealm());

        return defaultSecurityManager;
    }

    /**
     * 密码加解密规则
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        // 设置加密方式, 前端过来的明文密码进行加密和数据盘匹配, 数据库的密码也是加密后的
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        // 加密次数, 这里相当于 md5 两次
        hashedCredentialsMatcher.setHashIterations(2);
        return hashedCredentialsMatcher;
    }

    /**
     * 自定义 Realm
     */
    @Bean
    public CustomerRealm customerRealm() {
        CustomerRealm customerRealm = new CustomerRealm();
        // 设置加解密规则
        customerRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return customerRealm;
    }

    /**
     * 自定义 SessionManager
     */
    @Bean
    public SessionManager sessionManager() {
        CustomerSessionManager customerSessionManager = new CustomerSessionManager();

        // 超时时间, 默认 30 分钟, 30 分钟不操作该 session 过期, 这里设置为 20 秒方面测试
        customerSessionManager.setGlobalSessionTimeout(1000 * 20);

        // 配置 session 持久化
        customerSessionManager.setSessionDAO(redisSessionDAO());
        return customerSessionManager;
    }

    /**
     * 配置 RedisManager
     */
    public RedisManager getRedisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost("192.168.0.107");
        redisManager.setPort(6379);
        return redisManager;
    }

    /**
     * 自定义 CacheManager (使用上面配置的 RedisManager), 让自定义 realm 的认证和授权走缓存
     */
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(getRedisManager());

        //设置过期时间，单位是秒，60s
        redisCacheManager.setExpire(60);

        return redisCacheManager;
    }

    /**
     * 自定义 session 持久化
     * 1, 修改持久化方式: session_id 从内存移动到 redis 中, 这样重启服务 session_id 还是可以继续使用
     * 2, 设置自定义 session_id 生成方式
     */
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(getRedisManager());
        redisSessionDAO.setSessionIdGenerator(new CustomerSessionIdGenerator());
        return redisSessionDAO;
    }

    /**
     * 管理一些 shiro 的 bean 的生命周期
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 使 shiro 的注解生效, 比如 @RequireRoles
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        return new AuthorizationAttributeSourceAdvisor();
    }

    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setUsePrefix(true);
        return defaultAdvisorAutoProxyCreator;
    }
}
